#include "MiBand4/MiBand4.h"
#include "MiBand4/UUID.h"
#include <mbedtls/aes.h>
#include "MiBand4/Callbacks.h"
extern BaiduThings baidu;
uint8_t *_miband_key;
static uint16_t _steps, _calories, _distance;
static uint8_t heartrate;
static void logHex(uint8_t *pData, uint16_t length);
MiBand4 miband;
static void getMsgPacket(miband_app_icon_t appico, const char *title, const char *content, uint8_t *out, int &length)
{
    length = 3;
    out[0] = 0xFA;
    out[1] = 0x01;
    out[2] = appico;
    memcpy(out + length, title, strlen(title) + 1);
    length += strlen(title) + 1;
    memcpy(out + length, content, strlen(content) + 1);
    length += strlen(content) + 1;
}
void MiBand4::setKey(const uint8_t *key)
{
    memcpy(this->key, key, 16);
}
void MiBand4::setMAC(const char *mac)
{
    strcpy(this->mac_addr, mac);
}

void MiBand4::subscribeVoice()
{
    NimBLERemoteService *voiceControlServ = mibandClient->getService(voiceControlServUUID);
    NimBLERemoteCharacteristic *voiceControlChar1 = voiceControlServ->getCharacteristic(voiceEnterExitCharUUID);
    NimBLERemoteCharacteristic *voiceControlChar2 = voiceControlServ->getCharacteristic(voiceControlCharUUID);
    NimBLERemoteCharacteristic *voiceControlChar3 = voiceControlServ->getCharacteristic(voiceDataCharUUID);
    voiceControlChar1->subscribe(true, MiBand4::voiceEnterExitCallback);
    voiceControlChar2->subscribe(true, MiBand4::voiceControlCallback);
    voiceControlChar3->subscribe(true, MiBand4::voiceDataCallback);
}
void MiBand4::init()
{
    NimBLEDevice::init("ESP32");
    NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND);
    // NimBLEDevice::setPower(ESP_PWR_LVL_P6);
    mibandClient = NimBLEDevice::createClient();
    mibandClient->setClientCallbacks(new ClientCallbacks());
    // connect();
}
bool MiBand4::connect()
{
    // mibandClient->setConnectionParams(0x10a,0x10a, 5, 400);
    if (!mibandClient->connect(NimBLEAddress(std::string(mac_addr))))
    {
        return false;
    }
    _miband_key = key;
    // 获取并注册认证回调事件
    NimBLERemoteService *authServ = mibandClient->getService(authServUUID);
    NimBLERemoteCharacteristic *authChar = authServ->getCharacteristic(authCharUUID);

    authChar->subscribe(true, MiBand4::authNotifyCallback);
    MiBand4::authNotifyCallback(authChar, NULL, 0, true);

    // 获取并注册状态回调事件
    NimBLERemoteService *statusServ = mibandClient->getService(statusServUUID);
    NimBLERemoteCharacteristic *statusChar = statusServ->getCharacteristic(statusCharUUID);

    statusChar->subscribe(true, MiBand4::statusNotifyCallback);

    // 获取并注册心率回调事件
    NimBLERemoteService *heartRateServ = mibandClient->getService(heartRateServUUID);
    NimBLERemoteCharacteristic *heartRateChar = heartRateServ->getCharacteristic(heartRateCharUUID);
    heartRateChar->subscribe(true, MiBand4::heartRateNotifyCallback);

    // 注册语音回调
    subscribeVoice();
    int error;
    size_t size = opus_decoder_get_size(1);
    dec = (OpusDecoder *)ps_malloc(size);
    error = opus_decoder_init(dec, 16000, 1);
    if (error != OPUS_OK)
    {
        free(dec);
        dec = NULL;
    }
    else
        Serial.printf("已创建Opus解码器，大小：%d\n", size);
    return true;
}

void MiBand4::disconnect()
{
    mibandClient->disconnect();
    NimBLEDevice::deinit(false);
    // btStop();
}

/*
void MiBand4::refreshStatus()
{
    NimBLERemoteService *stepServ = mibandClient->getService(Serv1UUID);
    NimBLERemoteCharacteristic *stepChar = stepServ->getCharacteristic(stepUUID);
}
*/
void MiBand4::getBattery(miband_battery_t *result)
{
    NimBLERemoteService *battServ = mibandClient->getService(Serv1UUID);
    NimBLERemoteCharacteristic *battChar = battServ->getCharacteristic(batteryUUID);
    auto v = battChar->readValue();
    if (v.length() == 20)
        memcpy(result, v.data(), 20);
}

uint16_t MiBand4::getSteps()
{
    return _steps;
}

uint16_t MiBand4::getCalories()
{
    return _calories;
}

uint16_t MiBand4::getDistance()
{
    return _distance;
}

uint8_t MiBand4::getHeartRate()
{
    return heartrate;
}

#define MAX_CHUNKLENGTH 17
void MiBand4::writeChunked(uint8_t type, const uint8_t *data, size_t size)
{
    NimBLERemoteService *chunkServ = mibandClient->getService(Serv1UUID);
    NimBLERemoteCharacteristic *chunkChar = chunkServ->getCharacteristic(chunkedTransferUUID);
    uint8_t buf[MAX_CHUNKLENGTH + 3];
    uint8_t flag;
    uint16_t count = 0;
    for (size_t p = 0; p < size;)
    {
        size_t to_send = size - p;
        flag = 0;
        if (to_send <= MAX_CHUNKLENGTH)
        {
            flag |= 0x80;
            if (count == 0)
                flag |= 0x40;
        }
        else
        {
            to_send = MAX_CHUNKLENGTH;
            if (count > 0)
                flag |= 0x40;
        }
        buf[0] = 0;
        buf[1] = flag | type;
        buf[2] = count & 0xff;
        memcpy(buf + 3, data + p, to_send);
        chunkChar->writeValue(buf, to_send + 3, false);
        ++count;
        p += to_send;
    }
}

void MiBand4::sendNotify(const char *title, const char *message, miband_app_icon_t ico)
{
    NimBLERemoteService *alertServ = mibandClient->getService(alertServUUID);
    NimBLERemoteCharacteristic *alertChar = alertServ->getCharacteristic(alertCharUUID);
    uint8_t msg[512] = {0};
    int length = 0;
    getMsgPacket(ico, title, message, msg, length);
    alertChar->writeValue(msg, length, true);
}

void MiBand4::setAlarm(uint8_t slot, uint8_t hour, uint8_t minute, miband_alarm_repeat_t repeat, bool enabled, bool snooze)
{
    NimBLERemoteService *alarmServ = mibandClient->getService(Serv1UUID);
    NimBLERemoteCharacteristic *alarmChar = alarmServ->getCharacteristic(configurationUUID);
    uint8_t buf[5];

    if (enabled)
    {
        slot |= 0x80;
        if (snooze == false)
            slot |= 0x40;
    }
    buf[0] = 2;
    buf[1] = slot;
    buf[2] = hour;
    buf[3] = minute;
    buf[4] = repeat;
    LOG_PRINT("正在设置闹钟：");
    logHex(buf, 5);
    alarmChar->writeValue(buf, 5);
}

void MiBand4::setRemind(uint8_t slot, uint8_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, const char *message, miband_remind_config_t config)
{
    if (message == NULL)
        message = "未设置提醒内容";
    uint16_t msglen = strlen(message);
    uint16_t year1 = 2000 + year;
    uint8_t buf[512] = {0};
    buf[0] = 0x0b;
    buf[1] = slot;
    buf[2] = (config)&0xff;
    buf[3] = (config >> 8) & 0xff;
    buf[4] = (config >> 16) & 0xff;
    buf[5] = (config >> 24) & 0xff;
    buf[6] = (year1)&0xff;
    buf[7] = (year1 >> 8) & 0xff;
    buf[8] = month;
    buf[9] = day;
    buf[10] = hour;
    buf[11] = minute;
    buf[12] = 0;
    memcpy(buf + 13, message, msglen);
    msglen += 14;
    LOG_PRINT("正在设置提醒\n");
    logHex(buf, msglen);
    writeChunked(2, buf, msglen);
}

void MiBand4::setTime(uint8_t year, uint8_t month, uint8_t day, uint8_t ISOWeek, uint8_t hour, uint8_t minute, uint8_t second)
{
    miband_time_t t = {
        .year = year,
        .month = month,
        .day = day,
        .hour = hour,
        .minute = minute,
        .second = second};
    t.year += 2000;
    uint8_t buf[11] = {0};
    memcpy(buf, &t, 7);
    buf[7] = ISOWeek;
    NimBLERemoteService *timeServ = mibandClient->getService(Serv1UUID);
    NimBLERemoteCharacteristic *timeChar = timeServ->getCharacteristic(timeUUID);
    LOG_PRINT("正在设置时间\n");
    logHex(buf, 11);
    timeChar->writeValue(buf, 11, true);
}

void MiBand4::setWeather(time_t updateTime, miband_weather_t *weather, uint8_t count, int8_t timezonehour)
{
    uint8_t buf[512] = {0};
    uint16_t pos;
    buf[0] = 0x01;
    buf[1] = (updateTime)&0xff;
    buf[2] = (updateTime >> 8) & 0xff;
    buf[3] = (updateTime >> 16) & 0xff;
    buf[4] = (updateTime >> 24) & 0xff;
    buf[5] = timezonehour * 4;
    buf[6] = count;
    pos = 7;
    for (uint8_t i = 0; i < count; ++i)
    {
        buf[pos++] = weather[i].icon;
        buf[pos++] = weather[i].icon;
        buf[pos++] = weather[i].maxTemp;
        buf[pos++] = weather[i].minTemp;
        strcpy((char *)(buf + pos), weather[i].weather_desc);
        pos += strlen(weather[i].weather_desc) + 1;
    }
    LOG_PRINT("正在发送天气\n");
    writeChunked(1, buf, pos);
}

void MiBand4::setWeatherLocation(const char *location)
{
    uint8_t buf[256] = {0};
    uint16_t pos;
    buf[0] = 0x08;
    strcpy((char *)(buf + 1), location);
    pos = strlen(location) + 2;
    writeChunked(1, buf, pos);
}

void MiBand4::enableRecording(bool start)
{
    NimBLERemoteService *Serv = mibandClient->getService(voiceControlServUUID);
    NimBLERemoteCharacteristic *Char = Serv->getCharacteristic(voiceControlCharUUID);
    uint8_t d;
    if (start)
    {
        d = 0x02;
        Char->writeValue(&d, 1, false);
    }
    else
    {
        d = 0x03;
        Char->writeValue(&d, 1, false);
    }
}

void MiBand4::sendXiaoAiResultText(const char *text)
{
    size_t length = strlen(text);
    uint8_t packet[512];
    packet[0] = 0;
    packet[1] = 0;
    packet[2] = 0;
    strcpy((char *)(packet + 3), text);
    length += 3;
    packet[length] = 0;
    writeChunked(5, packet, length + 4);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void task_stopRecord(void *)
{
    miband.enableRecording(false);
    vTaskDelete(NULL);
}
bool taskIsRunning = false;
char base64[VOICE_MALLOC_SIZE * 4 / 3 + 10];
#define XIAOAI_REMIND_SLOT 2

static bool getTime(const char *timestr, struct tm *res)
{
    //"2022-12-26|09:00:00"
    int r = sscanf(timestr, "%04d-%02d-%02d|%02d:%02d:%02d", &(res->tm_year), &(res->tm_mon), &(res->tm_mday), &(res->tm_hour), &(res->tm_min), &(res->tm_sec));
    if (r != 6)
    {
        return false;
    }
    return true;
}

static void task_doVoiceRecognise(void *)
{
    if (WiFi.isConnected() == false)
    {
        miband.voiceFinished = true;
        miband.sendXiaoAiResultText("未连接WiFi");
        taskIsRunning = false;
        vTaskDelete(NULL);
        return;
    }
    taskIsRunning = true;
    HTTPClient http;
    if (miband.dec == NULL)
    {
        Serial.println("无法解码");
        miband.sendXiaoAiResultText("opus解码器实例不存在");
        taskIsRunning = false;
        vTaskDelete(NULL);
        return;
    }
    size_t pos_in = 0;
    size_t pos_out = 0;
    while (1)
    {
        size_t size_in = *(miband.voiceData + pos_in);
        int size = opus_decode(miband.dec, miband.voiceData + pos_in + 1, size_in, miband.PCMData + pos_out, 1280, 0);
        pos_in = pos_in + 1 + size_in;
        pos_out = pos_out + size;
        if (pos_out > 512 * 1024)
        {
            Serial.println("内存不足");
            miband.sendXiaoAiResultText("内存不足");
            taskIsRunning = false;
            vTaskDelete(NULL);
            return;
        }
        if (size < 0)
        {
            Serial.println("解码错误");
            miband.sendXiaoAiResultText("解码错误");
            taskIsRunning = false;
            vTaskDelete(NULL);
            return;
        }
        if (pos_in == miband.voiceDataSize)
            break;
        if (pos_in > miband.voiceDataSize)
        {
            Serial.print("数据错误，期望：");
            Serial.print(miband.voiceDataSize);
            Serial.println("，而实际：");
            Serial.print(pos_in);
            miband.sendXiaoAiResultText("数据错误");
            taskIsRunning = false;
            vTaskDelete(NULL);
            return;
        }
    }
    if (baidu.hasToken() == false)
    {
        if (baidu.getToken() == false)
        {
            miband.sendXiaoAiResultText("无法获取百度Token");
            Serial.println("无法获取百度Token");
            taskIsRunning = false;
            vTaskDelete(NULL);
        }
    }
    String s = baidu.getAsr((uint8_t *)miband.PCMData, pos_out * 2);
    if (s == "")
    {
        miband.sendXiaoAiResultText("没有识别到语音");
        taskIsRunning = false;
        vTaskDelete(NULL);
    }
    // 下面是一些固定的对话内容
    if (s.indexOf("更新天气") != -1)
    {
        miband.weatherUpdateReq = true;
        miband.sendXiaoAiResultText("已向系统发送更新天气请求");
        taskIsRunning = false;
        vTaskDelete(NULL);
    }
    else if (s.indexOf("重置对话") != -1)
    {
        baidu.bot_session_id = "";
        miband.sendXiaoAiResultText("重置对话状态成功");
        taskIsRunning = false;
        vTaskDelete(NULL);
    }
    else if (s.indexOf("继续") != -1)
    {
        if (baidu.last_chat_remain == "")
            miband.sendXiaoAiResultText("emm, 说完了");
        else
        {
            miband.sendXiaoAiResultText(baidu.last_chat_remain.substring(0, 250).c_str());
            baidu.last_chat_remain = baidu.last_chat_remain.substring(247, 10240);
        }
        taskIsRunning = false;
        vTaskDelete(NULL);
    }
    if (baidu.getBot(s))
    {
        if (baidu.obj["schema"]["intents"][0]["intent_name"] == "SET_REMIND")
        {
            struct tm settime;
            bool timeAvail = false;
            String content = "提醒";
            for (int i = 0; i < baidu.obj["schema"]["intents"][0]["slots"].size(); ++i)
            {
                auto obj1 = baidu.obj["schema"]["intents"][0]["slots"][i];
                if (obj1["slot_name"] == "user_remind_time")
                {
                    timeAvail = getTime(obj1["slot_values"][0]["normalized_word"], &settime);
                    break;
                }
                else if (obj1["slot_name"] == "user_wild_content")
                {
                    content = obj1["slot_values"][0]["normalized_word"].as<String>();
                }
            }
            if (timeAvail == false)
            {
                miband.sendXiaoAiResultText("无法获取提醒时间");
                taskIsRunning = false;
                vTaskDelete(NULL);
            }
            miband.setRemind(XIAOAI_REMIND_SLOT, settime.tm_year - 2000, settime.tm_mon, settime.tm_mday, settime.tm_hour, settime.tm_min, content.c_str());
            uint8_t packet[512];
            packet[0] = 0;
            packet[1] = 0x03;
            packet[2] = 0x01;
            uint16_t msglen = content.length();
            uint16_t sl = msglen;
            uint16_t year1 = settime.tm_year;
            char buf[256];
            buf[0] = XIAOAI_REMIND_SLOT;
            buf[1] = 0x09;
            buf[2] = 0;
            buf[3] = 0;
            buf[4] = 0;
            buf[5] = (year1)&0xff;
            buf[6] = (year1 >> 8) & 0xff;
            buf[7] = settime.tm_mon;
            buf[8] = settime.tm_mday;
            buf[9] = settime.tm_hour;
            buf[10] = settime.tm_min;
            buf[11] = 0;
            memcpy(buf + 12, content.c_str(), msglen + 1);
            msglen += 13;
            memcpy(packet + 3, buf, msglen);
            msglen += 3;
            memcpy(packet + msglen, content.c_str(), sl + 1);
            logHex(packet, msglen + sl + 1);
            miband.writeChunked(5, packet, msglen + sl + 1);
        }
        else
        {
            if (baidu.obj["schema"]["intents"][0]["intent_name"] == "DELETE_REMIND")
            {
                miband.setRemind(XIAOAI_REMIND_SLOT, 0, 0, 0, 0, 0, 0, 0);
            }
            miband.sendXiaoAiResultText(baidu.obj["actions"][0]["say"].as<String>().substring(0, 250).c_str());
            baidu.last_chat_remain = baidu.obj["actions"][0]["say"].as<String>().substring(247, 10240);
        }
    }
    else
    {
        miband.sendXiaoAiResultText("无法获取机器人回复");
    }
    taskIsRunning = false;
    vTaskDelete(NULL);
}

static void getAuthKey(uint8_t in[16], uint8_t out[16], uint8_t key[16])
{
    esp_aes_context ctx;
    esp_aes_init(&ctx);
    esp_aes_setkey(&ctx, key, 128);
    esp_aes_crypt_ecb(&ctx, ESP_AES_ENCRYPT, in, out);
}

static void logHex(uint8_t *pData, uint16_t length)
{
    LOG_PRINT("[%d] ", length);

    for (int i = 0; i < length; ++i)
    {
        LOG_PRINT("%02x ", pData[i]);
    }
    LOG_PRINT("\n");
}

void MiBand4::authNotifyCallback(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)
{
    if (length == 0)
    { // Start verification
        uint8_t startAuth[] = {0x01, 0x00};
        pBLERemoteCharacteristic->writeValue((uint8_t *)startAuth, 2, false);
    }
    else if (length == 20 && pData[0] == 0x10 && pData[1] == 0x01 && pData[2] == 0x81)
    {
        uint8_t reqRndNun[] = {0x82, 0x00, 0x02};
        pBLERemoteCharacteristic->writeValue((uint8_t *)reqRndNun, 3, false);
    }
    else if (length == 19 && pData[0] == 0x10 && pData[1] == 0x82 && pData[2] == 0x01)
    {
        uint8_t inbuf[16] = {0};
        uint8_t outbuf[16] = {0};
        for (int i = 0; i < 16; ++i)
        {
            inbuf[i] = pData[i + 3];
        }
        getAuthKey(inbuf, outbuf, _miband_key);
        uint8_t rspAuthkey[18] = {0};
        rspAuthkey[0] = 0x83;
        rspAuthkey[1] = 0x00;
        for (int i = 0; i < 16; ++i)
        {
            rspAuthkey[i + 2] = outbuf[i];
        }
        pBLERemoteCharacteristic->writeValue((uint8_t *)rspAuthkey, 18, false);
    }
    else if (length == 3 && pData[0] == 0x10 && pData[1] == 0x83 && pData[2] == 0x01)
    {
        LOG_PRINT("手环认证成功\n");
    }
    else if (length == 3 && pData[0] == 0x10 && pData[1] == 0x83 && pData[2] == 0x08)
    {
        LOG_PRINT("手环认证失败\n");
        uint8_t reqRndNun[] = {0x82, 0x00, 0x02};
        pBLERemoteCharacteristic->writeValue(reqRndNun, 3);
    }
}

void MiBand4::statusNotifyCallback(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)
{
    logHex(pData, length);
    uint8_t low = pData[1];
    uint8_t high = pData[2];
    uint16_t val = (high << 8) | low;
    LOG_PRINT("当前步数: %d步\n", val);
    _steps = val;
    low = pData[9];
    high = pData[10];
    val = (high << 8) | low;
    LOG_PRINT("当前消耗: %d千卡\n", val);
    _calories = val;
    low = pData[5];
    high = pData[6];
    val = (high << 8) | low;
    LOG_PRINT("当前距离: %d千米\n", val / 1000);
    _distance = val;
}

void MiBand4::heartRateNotifyCallback(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)
{
    logHex(pData, length);
    uint8_t val = pData[1];
    LOG_PRINT("当前心率: %dbpm\n", val);
    heartrate = val;
}

void MiBand4::voiceEnterExitCallback(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)
{
    NimBLERemoteService *Serv = miband.mibandClient->getService(voiceControlServUUID);
    NimBLERemoteCharacteristic *Char = Serv->getCharacteristic(voiceControlCharUUID);
    LOG_PRINT("手环语音总回调--");
    logHex(pData, length);
    if (pData[0] == 0x01)
    {
        if (pData[1] == 0x00)
        {
            LOG_PRINT("手动停止录音\n");
            xTaskCreate(task_stopRecord, "Task_stopRecord", 2048, NULL, 1, NULL);
            return;
        }
        else if (pData[1] == 0x01)
        {
            uint8_t data[] = {0x01, 0x20, 0x19};
            Char->writeValue(data, 3, false);
            LOG_PRINT("进入小爱同学界面\n");
            miband.isinXiaoAi = true;
            miband.voiceDataSize = 0;
            return;
        }
        else if (pData[1] == 0x02)
        {
            LOG_PRINT("退出小爱同学界面\n");
            miband.isinXiaoAi = false;
            xTaskCreate(task_stopRecord, "Task_stopRecord", 2048, NULL, 1, NULL);
            return;
        }
    }
    else if (pData[0] == 0x02)
    {
        if (pData[1] == 0x00 && pData[2] == 0x02)
        {
            LOG_PRINT("删除事件提醒\n");
            miband.setRemind(XIAOAI_REMIND_SLOT, 0, 0, 0, 0, 0, 0, 0);
        }
    }
}

void MiBand4::voiceControlCallback(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)
{
    LOG_PRINT("手环语音控制回调--");
    logHex(pData, length);
    if (pData[1] == 0x01)
    {
        LOG_PRINT("开始录音\n");
        miband.voiceFinished = false;
        miband.enableRecording(true);
        return;
    }
    else if (pData[1] == 0x02)
    {
        LOG_PRINT("停止录音\n");
        if (miband.isinXiaoAi)
        {

            LOG_PRINT("语音数据有效！\n");
            if (taskIsRunning == false)
                xTaskCreate(task_doVoiceRecognise, "Task_XiaoAi", 12800, NULL, 1, NULL);
        }
        return;
    }
    else if (pData[1] == 0x03)
    {
        LOG_PRINT("停止录音--0x03\n");
        return;
    }
}

void MiBand4::voiceDataCallback(NimBLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)
{
    LOG_PRINT("\n手环语音数据回调");
    logHex(pData, length);
    if (miband.voiceData == NULL)
    {
        xTaskCreate(task_stopRecord, "Task_stopRecord", 2048, NULL, 1, NULL);
        LOG_PRINT("--错误：请先申请内存\n");
        return;
    }
    if (miband.voiceDataSize + length < VOICE_MALLOC_SIZE)
    {
        memcpy(miband.voiceData + miband.voiceDataSize, pData + 2, length - 2);
        miband.voiceDataSize += length - 2;
    }
    else
    {
        xTaskCreate(task_stopRecord, "Task_stopRecord", 2048, NULL, 1, NULL);
        LOG_PRINT("--警告：超过内存大小，停止录制\n");
    }
}
